import java.util.*;
import java.io.*;
public class Kernel{
//global variables
public static Resource deviceTable[]=new Resource[7];
public static int totalProcesses=0;
public static Queue newQueue;
public static Queue readyQueue;
public static Queue waitingQueue;
public static Queue exitQueue;
public static Queue runningQueue;
public static Process currentlyRunning; 
public static Memory memory;
public static int currMemoryUsed=0;
public static CPU cpu;
public static CI cli;
public static boolean flag=true;
public static int time=0;
public static String file_name="test.txt";
public static String output;
public static boolean done=true;

public static Process terminatedProcesses[] = new Process[1000];
public static int currentPos = 0;
	public static void main(String[] args){
	if(bootstrap() > -1){
		output = "System Initiated";
		System.out.println("SYSTEM INITIATED");
		try {
       			BufferedWriter out = new BufferedWriter(new FileWriter(file_name));
       			out.write(time + "~ "+ output);
       			out.close();
    		} catch (IOException e) {
   		}
	
		/**main while loop, will execute continuously
		This is where we will run our entire OS from**/
		cli.interpreter();
		while(flag){
			
			output="";
			time++;
			if(cli.flag){
   	 			if(cli.num_processes >0){
	   				for(int i=1;i<=cli.num_processes;i++)
	   				{
						generateProcess();
					}
					longterm();
					while((!newQueue.isEmpty())&&((memory.isMemoryAvailable(newQueue.whoIsNext().pcb.burstLengthRemaining))==0))
					{
						Process tempP;
						tempP=newQueue.removeFromFront();
						readyQueue.addToBack(tempP);
						memory.addProcess(tempP);
						time+=4;
						tempP.timeInNewQueue = time-tempP.timeEnteredQueue;
						tempP.currentQueue = 1;
						tempP.timeEnteredQueue = time;
					}
				}
				else if(cli.num_processes==0)
	      			{
					System.out.println("Shutting down");
					//Close the log file b4 you exit
					
					writeToLog("Shutting Down");
					
					int totalNewQueueTime = 0;
					int totalReadyQueueTime = 0;
					int totalRunningQueueTime = 0;
					int totalWaitingQueueTime = 0;
					int totalTerminatedQueueTime = 0;
					int totalProcessesLifespan = 0;
					int i = 0;
					while(terminatedProcesses[i] != null){
						totalNewQueueTime += terminatedProcesses[i].timeInNewQueue;
						totalReadyQueueTime += terminatedProcesses[i].timeInReadyQueue;
						totalRunningQueueTime += terminatedProcesses[i].timeInRunningQueue;
						totalWaitingQueueTime += terminatedProcesses[i].timeInWaitingQueue;
						totalTerminatedQueueTime += terminatedProcesses[i].timeInTerminatedQueue;
						totalProcessesLifespan += terminatedProcesses[i].timeTerminated-terminatedProcesses[i].timeCreated;
						
						i++;
	      			}
					
					writeToLog("\nAverage Time In New Queue: "+totalNewQueueTime/i+"\nAverage Time In Ready Queue: "+totalReadyQueueTime/i+"\nAverage Time In Running Queue: "+totalRunningQueueTime/i+"\nAverage Time In Waiting Queue: "+totalWaitingQueueTime/i+"\nAverage Time In Terminated Queue: "+totalTerminatedQueueTime/i+"\nAverage Lifespan: "+totalProcessesLifespan/i);
					System.exit(0);
		
	      			}
	      			cli.flag=false;
	      
	      		}
			//there are processes to be dealt with
	      		else if(!exitQueue.isEmpty()||!newQueue.isEmpty()||!runningQueue.isEmpty()||!waitingQueue.isEmpty()||!readyQueue.isEmpty()){
	 			//check that we have at least 1 process ready to execute
	 			if(!exitQueue.isEmpty())
				{
					//now deallocate all those resources that the process was using
	 				Process terminatedProcess = exitQueue.removeFromFront();
					memory.removeProcess(terminatedProcess);
					
					terminatedProcess.timeTerminated = time;
					if(terminatedProcess.currentQueue == 4)
						terminatedProcess.timeInTerminatedQueue = time-terminatedProcess.timeEnteredQueue;
					
					terminatedProcesses[currentPos++] = terminatedProcess;
				}
				else if(!newQueue.isEmpty()&&(time%150000==0) && !newQueue.isSorted()){
					longterm();
	 				//check if there is memory available for the next process in the new queue then add it to ready if there is
					while((!newQueue.isEmpty())&&((memory.isMemoryAvailable(newQueue.whoIsNext().pcb.burstLengthRemaining))==0))
					{
						Process tempP;
						tempP=newQueue.removeFromFront();
						
						readyQueue.addToBack(tempP);
						memory.addProcess(tempP);
						time+=4;
						tempP.timeInNewQueue = time-tempP.timeEnteredQueue;
						tempP.currentQueue = 1;
						tempP.timeEnteredQueue = time;
					}
				}
				
				else if(!readyQueue.isEmpty() || !waitingQueue.isEmpty()){
					if(!readyQueue.isEmpty()){
					  if(cpu.isAvailable()){
						  cpu.setToBusy();
						  currentlyRunning=readyQueue.removeFromFront();
						  runningQueue.addToBack(currentlyRunning);
						  
						  currentlyRunning.timeInReadyQueue += time-currentlyRunning.timeEnteredQueue;
						  currentlyRunning.currentQueue = 2;
						  currentlyRunning.timeEnteredQueue = time;
						  
						  
						  int status = cpu.execute(currentlyRunning);
						  if(status == -1){//Exception thrown during execution
							  System.out.println("EXCEPTION!");
							  exitQueue.addToBack(runningQueue.removeFromFront());
							  currentlyRunning.currentQueue = 4;
							  currentlyRunning.timeEnteredQueue = time;
							  currentlyRunning=null;
						  }
						  else if(status == 1){//I/O interrupt thrown during execution
							waitingQueue.addToBack(runningQueue.removeFromFront());
							
							System.out.println("I/O interrupt!");
							if(!waitingQueue.bankers()){
								System.out.println("I/O interrupt! FAILED");
								//Process could not be allocated resources safely
								exitQueue.addToBack(waitingQueue.remove(waitingQueue.size()-1));
							}
							else{
								System.out.println("I/O interrupt! PASSED");
								waitingQueue.addToWaiting(currentlyRunning);
								
							  	currentlyRunning.currentQueue = 3;
							  	currentlyRunning.timeEnteredQueue = time;
								currentlyRunning = null;
							}
						  }
			 			  //check if the process is finished executing
			 			   else if(currentlyRunning.pcb.burstLengthRemaining==0){
			 				  exitQueue.addToBack(runningQueue.removeFromFront());
			 				  
			 				  if(currentlyRunning.currentQueue == 2)
			 					  currentlyRunning.timeInRunningQueue += time-currentlyRunning.timeEnteredQueue;
			 				  currentlyRunning.currentQueue = 4;
							  currentlyRunning.timeEnteredQueue = time;
							  
							   output = "Process terminated normally ="+exitQueue.whoIsNext().pcb.p_number;
			 				   System.out.println("Process terminated normally ="+exitQueue.whoIsNext().pcb.p_number);
			 				   currentlyRunning=null;
			 			   }
			 			   else{
			 				   readyQueue.addToBack(runningQueue.removeFromFront());
				 				if(currentlyRunning.currentQueue == 2)
				 					currentlyRunning.timeInRunningQueue += 1;//time-currentlyRunning.timeEnteredQueue;
			 				  	currentlyRunning.currentQueue = 1;
			 				  	currentlyRunning.timeEnteredQueue = time;
			      				currentlyRunning=null;
			 			   }
			  			 cpu.setToFree();
			  		 }
				       }
				      if(!waitingQueue.isEmpty()){
				       	time++;
				       					
						//for each process in waiting check if the process is ready to be put back into ready
						while(!waitingQueue.isEmpty())
						{
						done=true;
						//for process r, check if every device c has been in use for long enough
						  	for(int c=0;c<deviceTable.length;c++)
						  	{
						  	  if((waitingQueue.nextElement(0).pcb.devicesInUse[c][1])>(time - deviceTable[c].getTime())){
							  	  System.out.println("NOT GOING TO FREE RESOURCES YET PROCESS="+waitingQueue.nextElement(0).pcb.p_number);
								  done=false;
							  	  break;
						  	  }
						  	}
						   if(done){
						     //free resources
							 Process waitingProcess = waitingQueue.removeFromWaiting();
						     if(waitingProcess.currentQueue == 3)
						    	 waitingProcess.timeInWaitingQueue += time - waitingProcess.timeEnteredQueue;
						     waitingProcess.currentQueue = 2;
						     waitingProcess.timeEnteredQueue = time;
						     readyQueue.addToBack(waitingProcess);
						   }
						   else{
							   break;
						   }
						}//end of for each process in waiting loop
				       }
				      
				}
	 			System.out.println("TIME! "+newQueue.isEmpty()+ " "+readyQueue.isEmpty()+ " "+runningQueue.isEmpty()+ " "+waitingQueue.isEmpty()+ " "+exitQueue.isEmpty()+ " ");
	 			System.out.println("CPU IS AVAILABLE: "+cpu.isAvailable());
	      	}
			else{
					cli.interpreter();
				}
			
			if(output != ""){
				writeToLog(output);
			}

	 	}//end of while
	
  	}
	//Bootstrap failure
		System.exit(0);	
	}
	
		public static int bootstrap(){
		//variable initialization
		cpu=new CPU();
		cli=new CI();
		memory=new Memory();
		newQueue=new Queue();
		readyQueue=new Queue();
		waitingQueue=new Queue();
		exitQueue=new Queue();
		runningQueue=new Queue();
		time = 0;
		
		//create log file
		//BufferedWriter log = new BufferedWriter(new FileWriter(file_name,true));
		
		//Initialize Resource Table
		deviceTable[0] = new Resource("readTape", 220, 30);
		deviceTable[1] = new Resource("writeTape", 320, 50);
		deviceTable[2] = new Resource("keyboard", 500, 80);
		deviceTable[3] = new Resource("mouse", 220, 30);
		deviceTable[4] = new Resource("monitor", 420, 80);
		deviceTable[5] = new Resource("readCD", 320, 60);
		deviceTable[6] = new Resource("writeCD", 520, 80);
		
		return 0;
	}
	
	public static int generateProcess() {
		Process p=new Process();
		newQueue.addToBack(p);
		
		p.timeCreated = time;
		p.currentQueue = 0;
		p.timeEnteredQueue = time;
		return 0;		
	}
	
	
	public static int allocateMemory(Process p) {	
		//check if memory will ever be available for the amount needed
		if(p.getSize() <= memory.totalMemory()) {		 
			//check if there is currently memory available.
			if(memory.isMemoryAvailable(p.getSize())<0) {
			p.pcb.pageTable=memory.addProcess(p);
				if(p.pcb.pageTable!=null)
				{
				//check if the other ressources that the program needs are available now
				//now add the process to the ready queue
				readyQueue.addToBack(p);
				p.pcb.state = 1;
				}
			} else {
				//memory is not currently available so add it to the new queue
				newQueue.addToBack(p);
			}
		} else {
			//Memory will never be available to allocate to this process
			return -1;
 		}
 		return 0;
	}
	
	public static int longterm(){
	
	newQueue.schedule();
	return 0;
	}
	
	public static void writeToLog(String output){
		try {
       			BufferedWriter out = new BufferedWriter(new FileWriter(file_name, true));
       			out.write("\n"+ time + "~ "+ output);
       			out.close();
    		} catch (IOException e) {
   		}
	}
	
	private static void logStats(Process p){
		
	}
	
 
}
